State Pattern(스테이트 패턴)

들어가기

이번 포스팅에서는 스테이트 패턴(State Pattern)에 대해서 알아보겠습니다.

본론

스테이트 패턴(State Pattern)을 이용하면 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있습니다.

마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다.

하나의 예시로 형광들을 만들어 보겠습니다.

형광등을 만들려면 우선 형광등의 행위(기능)를 분석할 필요가 있습니다.

  • On 스위치를 누를 경우, 불이 켜짐
  • Off 스위치를 누를 경우, 불이 꺼짐

만약 이미 형광등이 켜져있는 상태에서 On 스위치를 누른다면 그대로 켜져있고, Off도 마찬가지로 꺼져있는 상태에서 눌러도 형광등에는 변화가 없을것 입니다.

실제 이를 코드로 작성하기는 그렇게 어렵지 않습니다.

public class Light {
    private static int ON = 0;
    private static int OFF = 1;
    private int state; // 현재 형광등의 상태

    public Light() {
        state = OFF; // 초기 형광등은 OFF
    }

    public void on_button_pushed() {
        if(state == ON) {
            System.out.println("already ON state");
        } 
        else { // 형광등이 꺼져있는 상태
            System.out.println("Light ON");
            state = ON;    
        }
    }

    public void off_button_pushed() {
        if(state == OFF) {
            System.out.println("already OFF state");
        } 
        else { // 형광등이 켜져있는 상태
            System.out.println("Light OFF");
            state = OFF;    
        }
    }   
}

public class Client {
    public static void main(String[] args) {
        Light light = new light();
        light.off();
        light.on();
        light.off();
    }
}

하지만 다음과 같이 작성한 코드는 문제점을 가지고 있습니다.

  • 형광등에 새로운 상태가 추가된다면 ??

즉, OFF와 ON뿐만이 아니라 새롭게 취침등(SLEEPING)이 추가된다면 if ~ else 구문의 수정이 불가피합니다.

OFF 상태에서는 SLEEPING으로 갈수 없고 ON 상태에서 on_button을 한번 다시 누른다면 SLEEPING 상태로 넘어가는 것을 가정으로 코드를 작성하겠습니다.

public class Light {
    // 이전과 동일
    private static int SLEEPING = 2; // 취침등 추가

    public void on_button_pushed() {
        if(state == ON) {
            System.out.println("sleeping mode");
            state = SLEEPING;
        } 
        else if(state == SLEEPING) { // 상태 추가에 따른 if문 추가
            System.out.println("Light On");
            state = ON;
        }
        else { // 형광등이 꺼져있는 상태
            System.out.println("Light ON");
            state = ON;    
        }
    }
}

지금은 간단한 상태를 가지는 조건문이지만, 조건문이 복잡해 상태 변화가 숨어 있는 경우 상태 변화가 어떻게 이루어지는지 이해하기 어렵고 새로운 상태 추가에 맞춰 모든 메서드를 수정해야합니다.

그럼 어떻게 문제를 해결할 수 있을까요?

이때 사용하는 패턴이 스테이트 패턴(State Pattern)입니다.

이번 예 역시 무엇이 변하는가를 찾아야 합니다. 따라서 변하는 것을 찾아 캡슐화를 해줘야합니다.

목표는 현재 시스템이 어떤 상태에 있는지와 상관없게 구성하고 상태 변화에도 독립적이도록 코드를 수정하는 것입니다.

이를 위해서 형광등이 가질수 있는 상태를 클래스로 분리해 캡슐화합니다. 또한 상태에 의존적인 행위들(버튼)도 같이 두어 특정상태에 따른 행위를 구현합니다.

위의 다이어그램은 형광등 만들기를 스테이트 패턴(State Pattern)을 사용하여 설계한 것입니다.

어떻습니까? 이전에 봤던 스트래티지 패턴이랑 비슷하지 않나요?

스테이트 패턴(State Pattern)은 거의 스트래티지 패턴과 유사하다고 볼수 있습니다. 다만, 상태를 가지고 있어 그 상태에 따라 동작하는 방식이 달라집니다. 또, 외부에서 변화를 주는 것이 아닌 Light 클래스가 변화에 중심이 있다는 것이 차이점입니다.

다시 위에서 그린 다이어그램을 토대로 코드로 옮겨보겠습니다.

Light.java

public class Light {
    private State state;

    public Light() {
      state = new OFF();
    }

    public void setState(State state) {
      this.state = state;
    }

    public void on_button_pushed() {
      state.on_button_pushed(this);
    }

    public void off_button_pushed() {
      state.off_button_pushed(this);
    }
}

State.java

public interface State {
    public void on_button_pushed(Light light);
    public void off_button_pushed(Light light);
}

ON.java

public class ON implements State {
    @Override
    public void on_button_pushed(Light light) {
        System.out.println("already ON");
    }

    @Override
    public void off_button_pushed(Light light) {
        System.out.println("Light OFF");
        light.setState(new OFF());
    }
}

OFF.java

public class OFF implements State {
    @Override
    public void on_button_pushed(Light light) {
      System.out.println("Light ON");
      light.setState(new ON());
    }

    @Override
    public void off_button_pushed(Light light) {
      System.out.println("already OFF");
    }
}

위와 같은 코드는 시스템이 어떤 상태에 있는지와 무관하게 동작합니다. Light 클래스는 새로운 상태가 추가된다고해도 기존의 동작 방식이 바뀌지 않습니다.

정리하자면 스테이트 패턴(State Pattern)은 위에서 잠시 언급했던 것과 같이 상태에 따라 동작방식을 다르게 하는 패턴입니다. 어떤 행위를 수행할때 상태에 행위를 위임하는 방식입니다.

이를 위해, 스테이트 패턴(State Pattern)은 각 상태를 클래스로 분리하고 클래스에서 수행하는 행위들을 메서드로 구현하는 것입니다.

마치며

스테이트 패턴(State Pattern)에 대해 알아보았습니다. 추가적인 질문은 댓글로 남겨주세요.

출처

JAVA 객체지향 디자인패턴

Share